﻿using System;
using System.Net;
using System.Net.Sockets;

namespace Framework.Networking
{
    /// <summary>
    /// Abstract async server application.
    /// To inherit this class we only need to override OnAccept handler.
    /// </summary>
    public abstract class ISocketServer : ISocket
    {
        #region Fields

        /// <summary>
        /// Ip address we're listening to.
        /// </summary>
        protected IPAddress ip;

        /// <summary>
        /// Gets an ip address we're listening to.
        /// </summary>
        public IPAddress Ip { get { return ip; } }

        /// <summary>
        /// Port we're listening to.
        /// </summary>
        protected int port;

        /// <summary>
        /// Gets port we're listening to.
        /// </summary>
        public int Port { get { return port; } }

        /// <summary>
        /// The socket we'll bind to.
        /// </summary>
        protected Socket bindedSocket;

        /// <summary>
        /// Gets the socket we'll bind to.
        /// </summary>
        public Socket BindedSocket { get { return bindedSocket; } }

        /// <summary>
        /// Listen state.
        /// </summary>
        protected bool listening = false;

        /// <summary>
        /// Gets listen state.
        /// </summary>
        public bool Listening { get { return listening; } }

        #endregion

        /// <summary>
        /// Constructs a socket on desired ip address with port.
        /// </summary>
        /// <param name="ip">use IPAddress.Any for binding to all available interfaces</param>
        /// <param name="port">port to listen to, defaults to 29000</param>
        public ISocketServer(IPAddress ip, int port = 29000)
        {
            this.ip = ip;
            this.port = port;
            this.bindedSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Start accepting connections. Before binding checks if interface and port if free for listening.
        /// </summary>
        public virtual void Start()
        {
            if (!PortAvailable())
            {
                // TODO: logging and reporting
                //Console.WriteLine("Cannot start server on port {0}. Port is unavailable. ", port);
                return;
            }

            try
            {
                IPEndPoint endpoint = new IPEndPoint(ip, port);
                bindedSocket.Bind(endpoint);
                bindedSocket.Listen(100); // TODO: we need to listen more then 100 then we'll be ready — load from config (max_clients & max_client_pool).
                bindedSocket.BeginAccept(new AsyncCallback(OnAccept), bindedSocket);
                listening = true;
                //LogServise.Info("Server listening on: " + ip + ":" + port, true);
            }
            catch (Exception e) // TODO: it's a generic exception handler, we need to break them to Socket and others. 
            {
                //LogServise.Error("Network.Server.OnAccept(IAsyncResult)", e);
            }
        }

        /// <summary>
        /// Connection handler.
        /// </summary>
        /// <param name="ar">has a Socket instance inside ar.AsyncState</param>
        protected abstract void OnAccept(IAsyncResult ar);

        /// <summary>
        /// Stop accepting connections.
        /// </summary>
        public virtual void Stop()
        {
            try
            {
                bindedSocket.Close(60); // secs as i remember
            }
            catch (Exception e)
            {
                // TODO: logging and reporting
                // TODO: it's a generic exception handler, we need to break them to Socket and others. 
                //LogServise.Error("Network.Server.OnAccept(IAsyncResult)", e);
            }
        }

        /// <summary>
        /// Checks if interface and port if free for listening.
        /// </summary>
        /// <returns>true if interface and port is free</returns>
        public virtual bool PortAvailable()
        {
            IPEndPoint endpoint = new IPEndPoint(ip, port);
            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                s.Bind(endpoint);
                s.Listen(5);
                s.Close();
            }
            catch (SocketException se)
            {
                if (se.ErrorCode == 10048)
                {
                    return false;
                }
                // TODO: logging and reporting
                // we need to log messages here and also throw false.
            }

            return true;
        }


        #region depricated

        /*
        private void OnAccept(IAsyncResult ar) // remake to abstract to inherit in childs
        {
            Socket s = (Socket)ar.AsyncState;

            try
            {
                Socket c = s.EndAccept(ar);

                //LogServise.Info("Connection accepted from " + c.RemoteEndPoint);

                //Start client
                SocketStream client = new SocketStream(c);
                Thread thr = new Thread(new ThreadStart(client.Start));
                thr.Name = "C" + c.RemoteEndPoint;
                thr.Start();

                //Accept more clients
                s.BeginAccept(new AsyncCallback(OnAccept), s);
            }
            catch (Exception e)
            {
                //LogServise.Error("Network.Server.OnAccept(IAsyncResult)", e);
            }
        }*/
        
        #endregion

    }
}
